package com.flycms.modules.elastic.service.impl;

import com.flycms.common.utils.DateUtils;
import com.flycms.common.utils.StrUtils;
import com.flycms.common.utils.page.Pager;
import com.flycms.modules.elastic.domain.dto.SearchDTO;
import com.flycms.modules.elastic.service.IElasticSearchService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

import static com.flycms.common.utils.StrUtils.stringFilter;

/**
 * Created by kaifei sun.
 * Copyright (c) 2020, All Rights Reserved.
 * https://www.97560.com
 */
@Component
public class ElasticSearchServiceImpl  implements IElasticSearchService {
    /**
     * elk集群地址
     */
    @Value("${elasticsearch.ip}")
    private String hostName;

    /**
     * 端口
     */
    @Value("${elasticsearch.port}")
    private String port;

    /**
     * 节点名称
     */
    @Value("${elasticsearch.node.name}")
    private String name;

    private Logger log = LoggerFactory.getLogger(ElasticSearchServiceImpl.class);

    @Resource
    private RestHighLevelClient client;

    public static XContentBuilder topicMappingBuilder;

    static {
        try {
            topicMappingBuilder = JsonXContent.contentBuilder()
                    .startObject()
                    .startObject("properties")
                    .startObject("infoType")
                    .field("type", "text")
                    .startObject("groupId")
                    .field("type", "long")
                    .startObject("columnId")
                    .field("type", "long")
                    .startObject("userId")
                    .field("type", "long")
                    .startObject("title")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .field("index", "true")
                    .startObject("content")
                    .field("type", "text")
                    .field("analyzer", "ik_max_word")
                    .field("index", "true")
                    .startObject("createTime")
                    .field("type", "date")
                    .field("format", "yyyy-MM-dd HH:mm:ss")
                    .field("index", "true")
                    .endObject()
                    .endObject()
                    .endObject()
                    .endObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @PostConstruct
    public RestHighLevelClient instance() {
        if (this.client != null) return client;
        try {
            if (StringUtils.isEmpty(hostName) || StringUtils.isEmpty(port)) return null;
            client = new RestHighLevelClient(
                    RestClient.builder(new HttpHost(hostName, Integer.parseInt(port), "http")).setMaxRetryTimeoutMillis(60 * 60 * 1000));
            // 判断索引是否存在，不存在创建
            if (!this.existIndex()) this.createIndex("topic", topicMappingBuilder);
            return client;
        } catch (NumberFormatException e) {
            log.error(e.getMessage());
            return null;
        }
    }

    // 创建索引
    public boolean createIndex(String type, XContentBuilder mappingBuilder) {
        try {
            if (this.instance() == null) return false;
            CreateIndexRequest request = new CreateIndexRequest(name);
            request.settings(Settings.builder().put("index.number_of_shards", 1).put("index.number_of_shards", 5));
            if (mappingBuilder != null) request.mapping(type, mappingBuilder);
            CreateIndexResponse response = this.client.indices().create(request, RequestOptions.DEFAULT);
            return response.isAcknowledged();
        } catch (IOException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    // 检查索引是否存在
    public boolean existIndex() {
        try {
            if (this.instance() == null) return false;
            GetIndexRequest request = new GetIndexRequest();
            request.indices(name);
            request.local(false);
            request.humanReadable(true);
            return client.indices().exists(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    // 删除所有索引
    public boolean deleteAllIndex() {
        try {
            if (this.instance() == null) return false;
            DeleteIndexRequest request = new DeleteIndexRequest(name);
            request.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
            AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
            return response.isAcknowledged();
        } catch (IOException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    // 创建文档
    public void createDocument(String type, String id, Map<String, Object> source) {
        try {
            if (this.instance() == null) return;
            IndexRequest request = new IndexRequest(name, type, id);
            request.source(source);
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    // 更新文档
    public void updateDocument(String type, String id, Map<String, Object> source) {
        try {
            if (this.instance() == null) return;
            UpdateRequest request = new UpdateRequest(name, type, id);
            request.doc(source);
            client.update(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    // 删除文档
    public void deleteDocument(String type, String id) {
        try {
            if (this.instance() == null) return;
            DeleteRequest request = new DeleteRequest(name, type, id);
            client.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    // 批量创建文档
    public void bulkDocument(String type, Map<String, Map<String, Object>> sources) {
        try {
            if (this.instance() == null) return;
            BulkRequest requests = new BulkRequest();
            Iterator<String> it = sources.keySet().iterator();
            int count = 0;
            while (it.hasNext()) {
                count++;
                String next = it.next();
                IndexRequest request = new IndexRequest(name, type, next);
                request.source(sources.get(next));
                requests.add(request);
                if (count % 1000 == 0) {
                    client.bulk(requests, RequestOptions.DEFAULT);
                    requests.requests().clear();
                    count = 0;
                }
            }
            if (requests.numberOfActions() > 0) client.bulk(requests, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    // 批量删除文档
    public void bulkDeleteDocument(String type, Long[] ids) {
        try {
            if (this.instance() == null) return;
            BulkRequest requests = new BulkRequest();
            int count = 0;
            for (Long id : ids) {
                count++;
                DeleteRequest request = new DeleteRequest(name, type, String.valueOf(id));
                requests.add(request);
                if (count % 1000 == 0) {
                    client.bulk(requests, RequestOptions.DEFAULT);
                    requests.requests().clear();
                    count = 0;
                }
            }
            if (requests.numberOfActions() > 0) client.bulk(requests, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

     /**
     * 翻页查询
     *
     * @param pageNo
     * @param pageSize
     * @param keyword  要查询的内容
     * @param fields   要查询的字段，可以为多个
     * @return 分页对象 {@link Pager}
     */
    public Pager<SearchDTO> searchDocument(Integer pageNo, Integer pageSize, String keyword, String... fields) {
        Pager<SearchDTO> pager=new Pager(pageNo,pageSize);
        try {
            if (this.instance() == null) return new Pager<>();
            SearchRequest request = new SearchRequest(name);
            SearchSourceBuilder builder = new SearchSourceBuilder();
            builder.query(QueryBuilders.multiMatchQuery(keyword, fields));
            builder.highlighter(getHighlightBuilder("content",130,1,"title","content"));
            builder.from((pageNo - 1) * pageSize).size(pageSize);
            request.source(builder);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 总条数
            int totalCount = (int) response.getHits().getTotalHits();
            List<SearchDTO> dto = new ArrayList<SearchDTO>();
            SearchHits hits = response.getHits();
            SearchHit[] hitsHits = hits.getHits();
            for (SearchHit hitsHit : hitsHits) {
                SearchDTO entity = new SearchDTO();
                entity.setId(hitsHit.getId());
                entity.setGroupId(hitsHit.getSourceAsMap().get("groupId").toString());
                entity.setInfoType(hitsHit.getSourceAsMap().get("infoType").toString());
                entity.setColumnId(hitsHit.getSourceAsMap().get("columnId").toString());
                entity.setUserId(hitsHit.getSourceAsMap().get("userId").toString());
                Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
                if(highlightFields!=null||highlightFields.size()!=0){
                    HighlightField highlightField = highlightFields.get("title");
                    if(highlightField!=null){
                        Text[] fragments = highlightField.getFragments();
                        StringBuilder stringBuilder=new StringBuilder();
                        for (Text text : fragments) {
                            stringBuilder.append(text);
                        }
                        entity.setTitle(stringBuilder.toString());
                    }else{
                        entity.setTitle(hitsHit.getSourceAsMap().get("title").toString());
                    }
                    HighlightField contenthighlightField = highlightFields.get("content");
                    if(contenthighlightField!=null){
                        Text[] fragments = contenthighlightField.getFragments();
                        StringBuilder stringBuilder=new StringBuilder();
                        for (Text text : fragments) {
                            stringBuilder.append(text);
                        }
                        entity.setContent(StrUtils.trimHtml2Txt(stringFilter(stringBuilder.toString())));
                    }else{
                        entity.setContent(StrUtils.trimHtml2Txt(hitsHit.getSourceAsMap().get("content").toString()));
                    }
                }
                entity.setCreateTime(DateUtils.dealDateFormat(hitsHit.getSourceAsMap().get("createTime").toString()));
                dto.add(entity);;
            }

            pager.setTotal(totalCount);
            pager.setList(dto);
            return pager;
        } catch (IOException e) {
            log.error(e.getMessage());
            return new Pager<>();
        }
    }

    /**
     * 相关内容搜索
     *
     * @param pageNo
     * @param pageSize
     * @param keyword  要查询的内容
     * @param id  需要排除的id
     * @param fields   要查询的字段，可以为多个
     * @return 分页对象 {@link Pager}
     */
    public Pager<SearchDTO> relevantSearch(Integer pageNo, Integer pageSize, String keyword, String id, String... fields) {
        Pager<SearchDTO> pager=new Pager(pageNo,pageSize);
        try {
            if (this.instance() == null) return new Pager<>();
            SearchRequest request = new SearchRequest(name);
            SearchSourceBuilder builder = new SearchSourceBuilder();

            builder.query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.multiMatchQuery(keyword,fields))
                    .mustNot(QueryBuilders.termQuery("_id",Long.valueOf(id))));

            builder.from((pageNo - 1) * pageSize).size(pageSize);
            request.source(builder);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 总条数
            int totalCount = (int) response.getHits().getTotalHits();
            List<SearchDTO> dto = new ArrayList<SearchDTO>();
            SearchHits hits = response.getHits();
            SearchHit[] hitsHits = hits.getHits();
            for (SearchHit hitsHit : hitsHits) {
                SearchDTO entity = new SearchDTO();
                entity.setId(hitsHit.getId());
                entity.setTitle(hitsHit.getSourceAsMap().get("title").toString());
                dto.add(entity);;
            }

            pager.setTotal(totalCount);
            pager.setList(dto);
            return pager;
        } catch (IOException e) {
            log.error(e.getMessage());
            return new Pager<>();
        }
    }


    /**
     * 设置高亮字段
     *
     * @param contentfields  需要截取的字段
     * @param size   截取的字符数
     * @param numbr 片段数
     * @param fields 需要加亮的字段
     * @return
     */
    private HighlightBuilder getHighlightBuilder(String contentfields, int size,int numbr, String... fields) {
        // 高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        for (String field : fields) {
            if("content".equals(contentfields)){
                //高亮查询字段,截取字符数，片段数
                highlightBuilder.field(field,size,numbr);
            }else{
                //高亮查询字段
                highlightBuilder.field(field);
            }
        }

        highlightBuilder.requireFieldMatch(false);     //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<em>");   //高亮设置
        highlightBuilder.postTags("</em>");
        //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
        highlightBuilder.fragmentSize(800000); //最大高亮分片数
        highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段

        return highlightBuilder;
    }
}
